/*
 * SysCatalog.cpp
 *
 *  Created on: 22-Oct-2013
 *      Author: vikrantsingh
 */

#include "SysCatalog.h"
#include "../PageLayout/Heap.h"
#include "../PageLayout/Tuple.h"
#include "../Sysfiles/Schemautil.h"
#include "../dependency/ErrorLookupTable.h"
#include "../dependency/debug.h"
#include "../PageLayout/DirHeader.h"
#include "../BufferManager/BufferManager.h"
#include "../sql/CreateTable.h"
#include "../sql/TableScan.h"
#include "../sql/Delete.h"

#include <string>
#include <vector>

SysCatalog::SysCatalog() {
    // TODO Auto-generated constructor stub
    dbMainHeader_ = new DBMetaData(0);
    DEBUG("******before calling systableheap********");
    sysTableHeap = new Heap(dbMainHeader_->getSysTablePageNumber());
    DEBUG("******before calling syscolheap********");
    sysTableColHeap = new Heap(dbMainHeader_->getSysColumnsHeaderPageNumber());
    sysIndexheap = new Heap(dbMainHeader_->getSysIndexHeaderPageNumber());
    sysIndexColHeap = new Heap(
            dbMainHeader_->getSysIndexColumnsHeaderPageNumber());
    DEBUG("*****exiting SYscatalog constructor********");

}

SysCatalog::~SysCatalog() {
    // TODO Auto-generated destructor stub
    delete sysTableHeap;
    delete sysTableColHeap;
    delete sysIndexheap;
    delete sysIndexColHeap;
}

STATUS_CODE SysCatalog::addInitSysTableRecord() {
    //adding table details in sysTables
    Schema initTableSchema;
    SchemaUtil::getInitialSysTableSchema(initTableSchema);
    int sysTableId = sysTableHeap->getNextRecNo();
    DEBUG("Systable number: " << sysTableId);
    string value[] = {Util::int_to_string(sysTableId),
        initTableSchema.tableName.c_str(), Util::int_to_string(
        initTableSchema.numOfColumns), Util::int_to_string(
        dbMainHeader_->getSysTablePageNumber())};
    vector<string> vecs(value, &value[initTableSchema.numOfColumns]);
    Tuple tuple(initTableSchema, vecs);
    char *record = tuple.recordPreparation();
    DEBUG("\n***sys tab TUPLE RECORD AFTRE PREP"<<tuple.toString());
    RIDStructure rid;
    int err = sysTableHeap->insertRecord(rid, tuple.getLength(), record);
    if (err != SUCCESS)
        return err;
    //adding columns to sys columns -- can remove it no use
    Schema sysColSchema;
    SchemaUtil::getInitialSysColSchema(sysColSchema);
    for (int i = 0; i < initTableSchema.numOfColumns; i++) {
        string value[] = {Util::int_to_string(sysTableColHeap->getNextRecNo()),
            initTableSchema.columnNames.at(i), Util::int_to_string(
            initTableSchema.fieldTypes.at(i)), Util::int_to_string(
            initTableSchema.fieldLengths.at(i)),
            Util::int_to_string(sysTableId)};
        vector<string> vecs(value, &value[sysColSchema.numOfColumns]);
        Tuple tuple(sysColSchema, vecs);
        char *record = tuple.recordPreparation();
        DEBUG("\n***sys col TUPLE RECORD AFTRE PREP"<<tuple.toString());
        RIDStructure rid;
        int err = sysTableColHeap->insertRecord(rid, tuple.getLength(), record);
        if (err != SUCCESS)
            return err;
    }
    dbMainHeader_->updateNumberOfTables(dbMainHeader_->getNumberOfTables() + 1);
    return SUCCESS;
}

STATUS_CODE SysCatalog::addInitSysColRecord() {
    //adding table details in sysTables
    Schema initTableSchema;
    SchemaUtil::getInitialSysTableSchema(initTableSchema);
    Schema sysColSchema;
    SchemaUtil::getInitialSysColSchema(sysColSchema);
    int sysColTableId = sysTableHeap->getNextRecNo();
    string value[] = {Util::int_to_string(sysColTableId),
        sysColSchema.tableName.c_str(), Util::int_to_string(
        sysColSchema.numOfColumns), Util::int_to_string(
        dbMainHeader_->getSysColumnsHeaderPageNumber())};
    vector<string> vecs(value, &value[initTableSchema.numOfColumns]);
    Tuple tuple(initTableSchema, vecs);
    char *record = tuple.recordPreparation();
    RIDStructure rid;
    int err = sysTableHeap->insertRecord(rid, tuple.getLength(), record);
    if (err != SUCCESS)
        return err;
    //adding columns to sys columns -- can remove it no use
    for (int i = 0; i < sysColSchema.numOfColumns; i++) {
        string value[] = {Util::int_to_string(sysTableColHeap->getNextRecNo()),
            sysColSchema.columnNames.at(i), Util::int_to_string(
            sysColSchema.fieldTypes.at(i)), Util::int_to_string(
            sysColSchema.fieldLengths.at(i)), Util::int_to_string(
            sysColTableId)};
        vector<string> vecs(value, &value[sysColSchema.numOfColumns]);
        Tuple tuple(sysColSchema, vecs);
        char *record = tuple.recordPreparation();
        RIDStructure rid;
        int err = sysTableColHeap->insertRecord(rid, tuple.getLength(), record);
        if (err != SUCCESS)
            return err;
    }
    dbMainHeader_->updateNumberOfTables(dbMainHeader_->getNumberOfTables() + 1);
    return SUCCESS;
}

STATUS_CODE SysCatalog::addInitTableIndexRecord() {
    CreateTable createTable;
    QueryStructures executeStruct;
    SchemaUtil::getInitialSysIndexSchema(executeStruct.schema);
    int err = createTable.run(&executeStruct);
    return err;
}

STATUS_CODE SysCatalog::addInitTableColIndexRecord() {
    CreateTable createTable;
    QueryStructures executeStruct;
    SchemaUtil::getInitialSysIndexColSchema(executeStruct.schema);
    int err = createTable.run(&executeStruct);
    return err;
}

STATUS_CODE SysCatalog::addTableEntry(Schema& schema) {
    DEBUG("BEFORE CREATING TABLE "<<dbMainHeader_->toString());
    Schema temp;
    int err = getSchema(schema.tableName.c_str(), temp);
    if (err == TABLE_EXISTS)
        return err;
    //create a new DirPage

    int newDirHeaderPgno = -1;
    if (schema.tableName == "SYSINDEX") {

        newDirHeaderPgno = sysIndexheap->dirHeader_->getPageNo();

    } else if (schema.tableName == "SYSINDEXCOL") {

        newDirHeaderPgno = sysIndexColHeap->dirHeader_->getPageNo();
    } else {
        DirHeader dirHeader;
        dirHeader.createDirectoryHeaderPage();
        newDirHeaderPgno = dirHeader.getPageNo();
    }

    int tableId = sysTableHeap->getNextRecNo();
    string values[] = {Util::int_to_string(tableId), schema.tableName,
        Util::int_to_string(schema.numOfColumns), Util::int_to_string(
        newDirHeaderPgno)};
    // add into sysTable
    Schema sysTableSchema;
    err = getSchema("systable", sysTableSchema);
    vector<string> vecs(values, &values[sysTableSchema.numOfColumns]);
    if (err != SUCCESS)
        return err;
    Tuple tuple(sysTableSchema, vecs);
    char * rec = tuple.recordPreparation();
    RIDStruct rid;
    err = sysTableHeap->insertRecord(rid, tuple.getLength(), rec);
    if (err != SUCCESS)
        return err;
    // adding into syscol
    Schema sysColSchema;
    SchemaUtil::getInitialSysColSchema(sysColSchema);
    for (int i = 0; i < schema.numOfColumns; i++) {
        string value[] = {Util::int_to_string(sysTableColHeap->getNextRecNo()),
            schema.columnNames.at(i), Util::int_to_string(
            schema.fieldTypes.at(i)), Util::int_to_string(
            schema.fieldLengths.at(i)), Util::int_to_string(
            tableId)};
        vector<string> vecs(value, &value[sysColSchema.numOfColumns]);
        Tuple tuple(sysColSchema, vecs);
        char *record = tuple.recordPreparation();
        RIDStructure rid;
        int err = sysTableColHeap->insertRecord(rid, tuple.getLength(), record);
        if (err != SUCCESS)
            return err;
    }
    dbMainHeader_->updateNumberOfTables(dbMainHeader_->getNumberOfTables() + 1);
    DEBUG("AFTER CREATING TABLE "<<dbMainHeader_->toString());
    return SUCCESS;

}

STATUS_CODE SysCatalog::deleteTableEntry(Schema& schema) {
    //BufferManager *bm = BufferManager::getInstance();
    Heap toBeDeleted(schema.tableName.c_str());
    toBeDeleted.deleteHeap();

    int tableId;
    int err = getTableId(schema.tableName.c_str(), tableId);
    if (err != SUCCESS)
        return err;
    QueryStructures executeStruct;
    executeStruct.tableName = "SYSCOL";
    executeStruct.schema = schema;
    executeStruct.statementType = QueryStructures::DELETE_STATEMENT;
    ExprInfo exprInfo1(ExprInfo::IDENTIFIER_TYPE, "TAB_ID");
    ExprInfo exprInfo2(ExprInfo::LITERAL_TYPE, Util::int_to_string(tableId));
    ExprInfo exprInfo3(ExprInfo::OPERATOR_TYPE, "=");
    executeStruct.whereConditionExprList.push_back(exprInfo1);
    executeStruct.whereConditionExprList.push_back(exprInfo2);
    executeStruct.whereConditionExprList.push_back(exprInfo3);
    Delete deleteStatement1;
    err = deleteStatement1.run(&executeStruct);
    if (err != SUCCESS) {
        DEBUG("error" << ErrorLookupTable::lookup(err).c_str());
        return err;
    }

    //delete entry from systable
    QueryStructures executeStruct2;
    executeStruct2.tableName = "SYSTABLE";
    executeStruct2.schema = schema;
    executeStruct2.statementType = QueryStructures::DELETE_STATEMENT;
    ExprInfo exprInfo11(ExprInfo::IDENTIFIER_TYPE, "TABLE_NAME");
    ExprInfo exprInfo21(ExprInfo::LITERAL_TYPE, schema.tableName);
    ExprInfo exprInfo31(ExprInfo::OPERATOR_TYPE, "=");
    executeStruct2.whereConditionExprList.push_back(exprInfo11);
    executeStruct2.whereConditionExprList.push_back(exprInfo21);
    executeStruct2.whereConditionExprList.push_back(exprInfo31);
    Delete deleteStatement2;
    err = deleteStatement2.run(&executeStruct2);
    if (SUCCESS != err) {
        DEBUG("error" << ErrorLookupTable::lookup(err).c_str());
        return err;
    }

    //updating statistical data
    dbMainHeader_->updateNumberOfTables(
            dbMainHeader_->getNumberOfTables() - 1);
    return err;

}

STATUS_CODE SysCatalog::addIndexEntry(QueryStructures* executeStruct) {
    return FAILURE;
}

STATUS_CODE SysCatalog::deleteIndexEntry(QueryStructures* executeStruct) {
    return FAILURE;
}

STATUS_CODE SysCatalog::getAllTables(vector<string>& tableList) {
    DEBUG("indise CatalogUtil::getListOfTables");
    Schema sysTabSchema;
    getSchema("systable", sysTabSchema);
    TableScan tableScan(sysTableHeap);
    char *record;
    unsigned recordLen;
    while (SCAN_OVER != tableScan.getNextRec(record, recordLen)) {
        Tuple tuple(sysTabSchema, record, recordLen);
        char tableName[sysTabSchema.fieldLengths.at(TABLE_NAME_POS)];
        tuple.getField(TABLE_NAME_POS, &tableName);
        
        DEBUG("\nTABLE NAME :"<<tableName);
        DEBUG("\nTuple :"<<tuple.toString());
        std::string str(tableName);
        tableList.push_back(str);
    }
    return SUCCESS;
}

int SysCatalog::getNextTableId() {
    return sysTableHeap->getNextRecNo();
}

STATUS_CODE SysCatalog::getTableId(const char* tableName, int & tableId) {
    Schema sysTableSchema;
    getSchema("SYSTABLE", sysTableSchema);
    TableScan tableScan(sysTableHeap);
    char* record;
    unsigned recLen;
    while (tableScan.getNextRec(record, recLen) != SCAN_OVER) {
        Tuple tuple(sysTableSchema, record, recLen);
        if (tuple.matchField(TABLE_NAME_POS, tableName) == true) {
            tuple.getField(TABLE_ID_POS, &tableId);
            return SUCCESS;
        }
    }
    return TABLE_NOT_FOUND;
}

STATUS_CODE SysCatalog::getSchema(const char* tableName, Schema& schema) {
    if (strcasecmp(tableName, "systable") == 0) {
        SchemaUtil::getInitialSysTableSchema(schema);
        return SUCCESS;
    } else if (strcasecmp(tableName, "syscol") == 0) {
        SchemaUtil::getInitialSysColSchema(schema);
        return SUCCESS;
    } else {
        Schema sysTableSchema;
        SchemaUtil::getInitialSysTableSchema(sysTableSchema);
        TableScan tableScan(sysTableHeap);
        char * record;
        unsigned recLen;
        int tableId;
        int err = tableScan.getNextRec(record, recLen);
        bool tableFound = false;
        while (err != SCAN_OVER) {
            if (err != SUCCESS)
                return err;
            Tuple tuple(sysTableSchema, record, recLen);
            if (tuple.matchField(TABLE_NAME_POS, tableName) == true) {
                tuple.getField(TABLE_ID_POS, &tableId);
                char *tableName = new char[sysTableSchema.fieldLengths.at(TABLE_NAME_POS)];
                tuple.getField(TABLE_NAME_POS, tableName);
                string temp(tableName);
                schema.tableName = temp;
                int noOfCols;
                tuple.getField(NO_OF_COLS_POS, &noOfCols);
                schema.numOfColumns = noOfCols;
                tableFound = true;
                break;
            }
            err = tableScan.getNextRec(record, recLen);
        }
        if (tableFound == false)
            return TABLE_NOT_FOUND;
        Schema sysColSchema;
        SchemaUtil::getInitialSysColSchema(sysColSchema);
        TableScan sysColTableScan(sysTableColHeap);
        char *sysColRecord;
        unsigned colRecLen;
        while (sysColTableScan.getNextRec(sysColRecord, colRecLen) != SCAN_OVER) {
            Tuple tuple(sysColSchema, sysColRecord, colRecLen);
            if (tuple.matchField(TAB_ID_POS, &tableId) == true) {
                char *colName = new char[sysColSchema.fieldLengths.at(COL_NAME_POS)];
                tuple.getField(COL_NAME_POS, colName);
                string name(colName);
                schema.columnNames.push_back(name);
                int fieldType;
                tuple.getField(COL_TYPE_POS, &fieldType);
                schema.fieldTypes.push_back(fieldType);
                int fieldLen;
                tuple.getField(COL_LENGTH_POS, &fieldLen);
                schema.fieldLengths.push_back(fieldLen);
            }
        }
    }
    return TABLE_EXISTS;
}

STATUS_CODE SysCatalog::getSchema(int dirHeaderpage, Schema& schema) {
    DEBUG("searching in get schema based on dir header page");
    Schema sysTableSchema;
    SchemaUtil::getInitialSysTableSchema(sysTableSchema);
    TableScan tableScan(sysTableHeap);
    char * record;
    unsigned recLen;
    int tableId;
    int err = tableScan.getNextRec(record, recLen);
    bool tableFound = false;
    while (err != SCAN_OVER) {
        if (err != SUCCESS)
            return err;
        Tuple tuple(sysTableSchema, record, recLen);
        if (tuple.matchField(HDR_PG_NO_POS, &dirHeaderpage) == true) {
            tuple.getField(TABLE_ID_POS, &tableId);
            char *tableName = new char[sysTableSchema.fieldLengths.at(TABLE_NAME_POS)];
            tuple.getField(TABLE_NAME_POS, tableName);
            string temp(tableName);
            schema.tableName = temp;
            int noOfCols;
            tuple.getField(NO_OF_COLS_POS, &noOfCols);
            schema.numOfColumns = noOfCols;
            tableFound = true;
            break;
        }
        err = tableScan.getNextRec(record, recLen);
    }
    if (tableFound == false)
        return TABLE_NOT_FOUND;
    Schema sysColSchema;
    SchemaUtil::getInitialSysColSchema(sysColSchema);
    TableScan sysColTableScan(sysTableColHeap);
    char *sysColRecord;
    unsigned colRecLen;
    while (sysColTableScan.getNextRec(sysColRecord, colRecLen) != SCAN_OVER) {
        Tuple tuple(sysColSchema, sysColRecord, colRecLen);
        if (tuple.matchField(TAB_ID_POS, &tableId) == true) {
            char *colName = new char[sysColSchema.fieldLengths.at(COL_NAME_POS)];
            tuple.getField(COL_NAME_POS, colName);
            string name(colName);
            schema.columnNames.push_back(name);
            int fieldType;
            tuple.getField(COL_TYPE_POS, &fieldType);
            schema.fieldTypes.push_back(fieldType);
            int fieldLen;
            tuple.getField(COL_LENGTH_POS, &fieldLen);
            schema.fieldLengths.push_back(fieldLen);
        }
    }
    return TABLE_EXISTS;
}

int SysCatalog::getHeaderPgNo(const char* tableName, int & headerPgNo) {
    Schema sysTable;
    getSchema("SYSTABLE", sysTable);
    TableScan tableScan(sysTableHeap);
    char * rec;
    unsigned recLen;
    while (tableScan.getNextRec(rec, recLen) != SCAN_OVER) {
        Tuple tuple(sysTable, rec, recLen);
        if (tuple.matchField(TABLE_NAME_POS, tableName) == true) {
            tuple.getField(HDR_PG_NO_POS, &headerPgNo);
            return SUCCESS;
        }
    }
    return TABLE_NOT_FOUND;
}

bool SysCatalog::isIndexExist(Schema& schema, string identifier,
        vector<int> indexHeaderPgNoList) {
    return false;
}
